package x.ovo.wechat.bot.impl.plugin;

import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.setting.yaml.YamlUtil;
import x.ovo.wechat.bot.core.exception.PluginException;
import x.ovo.wechat.bot.core.plugin.Plugin;
import x.ovo.wechat.bot.core.plugin.PluginDescription;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Objects;
import java.util.Optional;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 插件加载器
 *
 * @author ovo on 2024/07/09.
 */
@UtilityClass
public class PluginLoader {

    /**
     * 获取插件描述
     *
     * @param file 插件jar文件
     * @return {@link PluginDescription}
     */
    public static PluginDescription getDescription(File file) {
        try (JarFile jarFile = new JarFile(file)) {
            JarEntry entry = Optional.ofNullable(jarFile.getJarEntry("plugin.yml")).orElse(jarFile.getJarEntry("plugin.yaml"));
            Assert.notNull(entry, () -> PluginException.NOT_FOUND_YAML);
            try (InputStream is = jarFile.getInputStream(entry)) {
                return YamlUtil.load(is, PluginDescription.class);
            }
        } catch (IOException e) {
            throw new PluginException("插件jar加载失败", e);
        }
    }

    /**
     * 加载插件
     *
     * @param file        插件jar文件
     * @param description 描述
     * @return {@link Plugin}
     */
    @SneakyThrows
    public static Plugin load(File file, PluginDescription description) {
        URL[] urls = new URL[]{file.toURI().toURL()};
        // 为每一个插件单独创建一个类加载器进行隔离加载
        PluginClassLoader classLoader = new PluginClassLoader(urls);
        Thread.currentThread().setContextClassLoader(classLoader);
        // 加载插件主类
        Class<?> clazz = classLoader.loadClass(description.getMain());
        if (!Plugin.class.isAssignableFrom(clazz)) {
            throw PluginException.NOT_FOUND_MAIN;
        }
        // 实例化
        Plugin plugin = (Plugin) clazz.getDeclaredConstructor().newInstance();
        plugin.setClassLoader(classLoader);
        plugin.setPluginDesc(description);
        plugin.setPriority(Objects.isNull(description.getPriority()) ? 10 : description.getPriority());
        return plugin;
    }
}
